/***************************************************************************
 *   Copyright (C) 2008 by Jesse Crossen   *
 *   jesse.crossen@gmail.com   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "music.h"

Music::Music(QObject *parent)
 : QObject(parent)
{
  // set defaults
  referenceFrequency = 440.0;
  // set note names
  NoteNames << 
    "C" << "C#" << "D" << "Eb" << "E" << "F" << "F#" << "G" << "Ab" << "A" << "Bb" << "B";
  // size the note frequencies list
  for(int octave = 0; octave < 10; octave++) {
    for(int type = 0; type < 12; type++) {
      Frequencies << 0.0;
    }
  }
  // read configuration files
  readConfiguration();
  // build the scale
  buildScale();
}

Music::~Music()
{
}

// read settings from configuration files
void Music::readConfiguration() {
  readInstruments("instruments.xml");
}

// read instruments from a file
void Music::readInstruments(const char * path) {
  QFile file(path);
  if (! file.open(QFile::ReadOnly | QFile::Text)) {
    printf("Failed to read configuration.");
    return;
  }
  QXmlStreamReader xml(&file);
  while (! xml.atEnd()) {
    QXmlStreamReader::TokenType type = xml.readNext();
    if (type == QXmlStreamReader::StartElement) {
      // add instruments
      if (xml.name() == "instrument") 
        Instruments << readInstrument(&xml);
    }
  }
  file.close();
}
// read an instrument definition from a config file
//  (assumes the StartElement token for an 'instrument' 
//    element has just been read)
Music::Instrument Music::readInstrument(QXmlStreamReader * xml) {
  Instrument inst;
  // read the instrument name
  inst.name = xml->attributes().value("name").toString();
  while (! xml->atEnd()) {
    QXmlStreamReader::TokenType type = xml->readNext();
    // return when we reach the end of the instrument definition
    if (type == QXmlStreamReader::EndElement) break;
    // read rows
    else if (type == QXmlStreamReader::StartElement) {
      if (xml->name() == "tr")
        inst.strobeNotes << readInstrumentRow(xml);
      // skip past all unrecognized elements
      else xml->readElementText();
    }
  }
  return(inst);
}
// read a row of strobe settings from a config file
//  (assumes the StartElement token for a 'tr' 
//    element has just been read)
QList<int> Music::readInstrumentRow(QXmlStreamReader * xml) {
  QList<int> row;
  // read cells
  while (! xml->atEnd()) {
    QXmlStreamReader::TokenType type = xml->readNext();
    // return when we reach the end of the row
    if (type == QXmlStreamReader::EndElement) break;
    // read cells
    else if (type == QXmlStreamReader::StartElement) {
      if (xml->name() == "td")
        row << Note(xml->readElementText().toAscii());
      // skip past all unrecognized elements
      else xml->readElementText();
    }
  }
  return(row);
}

// fill the instrument selection dropdown
void Music::FillInstrumentCombo(QComboBox * combo) {
  for(int i = 0; i < Instruments.size(); i++) {
    combo->addItem(Instruments.at(i).name);
  }
}

// return an index into the scale based on a note name
int Music::Note(const char * name)
{
  // default to middle C
  int type = 0;
  int octave = 4;
  // read the note letter
  char c = name[0];
  // make sure we don't have a zero length string
  if (c != '\0') {
    if (c == 'C') type = 0;
    // (C# = 1)
    else if (c == 'D') type = 2;
    // (Eb = 3)
    else if (c == 'E') type = 4;
    else if (c == 'F') type = 5;
    // (F# = 6)
    else if (c == 'G') type = 7;
    // (Ab = 8)
    else if (c == 'A') type = 9;
    // (Bb = 10)
    else if (c == 'B') type = 11;
    // next comes either an accidental or the octave number
    c = name[1];
    if (c != '\0') {
      if (c == '#') {
        type++;
        c = name[2];
      }
      else if (c == 'b') {
        type--;
        c = name[2];
      }
      // next should be the octave number
      if ((c >= '0') && (c <= '9')) 
        octave = c - '0';
    }
  }
  // constrain the numbers
  type = (type + 12) % 12;
  octave = (octave + 10) % 10;
  // convert to a scale index
  return((octave * 12) + type);
}

// return the name of the note at the given scale index
QString Music::NoteName(int index)
{
  // bounds check
  if (index < 0) index = 0;
  // get the note type
  int type = index % 12;
  // get the octave
  int octave = index / 12;
  // look up the note name
  return(QString("%1%2").arg(NoteNames[type]).arg(octave));
}

// build the musical scale based on the current settings
void Music::buildScale(void)
{
  int i;
  // set the reference note to the reference frequency
  int refNote = Note("A4");
  Frequencies[refNote] = referenceFrequency;
  // get the current temperament
  float ratio = pow(2.0, (1.0 / 12.0));
  // set notes above the reference
  for(i = refNote + 1; i < Frequencies.size(); i++) {
    // see if this note is an octave of the reference
    if (i % 12 == refNote % 12) {
      // set the frequency by doubling to cut down on error buildup
      Frequencies[i] = 2.0 * Frequencies[i - 12];
    }
    else {
      // get the ratio that adds a semitone to the note below
      //  and multiply to get this frequency
      Frequencies[i] = Frequencies[i - 1] * ratio;
    }
  }
  // set notes below the reference
  for(i = refNote - 1; i >= 0; i--) {
    // see if this note is an octave of the reference
    if (i % 12 == refNote % 12) {
      // set the frequency by halving to cut down on error buildup
      Frequencies[i] = Frequencies[i + 12] / 2.0;
    }
    else {
      // get the ratio that subtracts a semitone from the note above
      //  and divide to get this frequency
      Frequencies[i] = Frequencies[i + 1] / ratio;
    }
  }
  // the scale has been changed
  emit scaleChanged();
}

// change the reference frequency
void Music::setReferenceFrequency(double frequency) {
  if (frequency < 1.0) frequency = 1.0;
  if (referenceFrequency != (float)frequency) {
    referenceFrequency = (float)frequency;
    buildScale();
  }
}
